package com.tjc.download.server.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author: tjc
 * @description
 * @Date Created in 23:00 2022-09-16
 */
@Controller
@RequestMapping("/download")
public class DownloadController {
    @Value("${fsPath}")
    String fsPath;

    @Value("${bufSize}")
    int bufSize;

    @RequestMapping("/1/{filename}")
    public void download1(@PathVariable(name = "filename") String filename, HttpServletResponse response) throws IOException {

        // 下载文件能正常显示中文
        File file = new File(fsPath, filename);
        InputStream input = new BufferedInputStream(new FileInputStream(file));

        response.setContentType("application/octet-stream; charset=UTF-8");
        response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", URLEncoder.encode(filename, "UTF-8")));
        response.setContentLengthLong(file.length());

        byte[] buf = new byte[1024 * 1024 * bufSize];
        int i = 0;
        while ((i = input.read(buf)) != -1) {
            response.getOutputStream().write(buf, 0, i);
        }
        input.close();
    }

    @RequestMapping("/2/{filename}")
    public void downloadSingleRange(@PathVariable(name = "filename") String filename, HttpServletResponse response,
                                    @RequestHeader(name = "Range", required = false) String range) throws IOException {

        // 下载文件能正常显示中文
        RandomAccessFile accessFile = new RandomAccessFile(new File(fsPath, filename), "r");
        List<Range> ranges = parseRange(range, accessFile.length());
        byte[] buf = new byte[1024 * 1024 * bufSize];

        if (ranges.size() == 1) {
            Range part = ranges.get(0);
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            response.setContentLengthLong(part.size());
            response.addHeader("Content-Range", "bytes " + part.start + "-" + part.end + "/" + accessFile.length());
            writeRange(response, buf, accessFile, part.start, part.size());
        } else if (ranges.size() > 1) {
            long contentLength = 0;
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            String boundary = "3d6b6a416f9b5";
            response.setContentType("multipart/byteranges; boundary=" + boundary);
            byte[] boundaryLine = ("--" + boundary + "\r\n").getBytes(StandardCharsets.UTF_8);
            byte[] contentTypeLine = ("Content-Type: " + "application/octet-stream" + "\r\n").getBytes(StandardCharsets.UTF_8);
            byte[] separate = "\r\n".getBytes(StandardCharsets.UTF_8);
            for (Range r : ranges) {
                byte[] contentRangeLine = ("Content-Range: " + "bytes " + r.start + "-" + r.end + "/" + accessFile.length() + "\r\n").getBytes(StandardCharsets.UTF_8);
                contentLength += boundaryLine.length;
                contentLength += contentRangeLine.length;
                contentLength += contentTypeLine.length;
                contentLength += separate.length;
            }
            contentLength += ranges.stream().mapToLong(Range::size).sum();
            String partContentType = "";
            countMultiRangeContentLength(ranges, boundary, partContentType);
            contentLength += ("--" + boundary + "--").getBytes(StandardCharsets.UTF_8).length;
            response.setContentLengthLong(contentLength);
            for (Range value : ranges) {
                response.getOutputStream().write(boundaryLine);
                response.getOutputStream().write(contentTypeLine);
                byte[] contentRangeLine = ("Content-Range: " + "bytes " + value.start + "-" + value.end + "/" + accessFile.length() + "\r\n").getBytes(StandardCharsets.UTF_8);
                response.getOutputStream().write(contentRangeLine);
                response.getOutputStream().write(separate);
                writeRange(response, buf, accessFile, value.start, value.size());
                response.getOutputStream().write("\r\n".getBytes(StandardCharsets.UTF_8));
            }
            response.getOutputStream().write(("--" + boundary + "--").getBytes(StandardCharsets.UTF_8));
        } else {
            response.setContentLengthLong(accessFile.length());
            response.setContentType("application/octet-stream; charset=UTF-8");
            writeRange(response, buf, accessFile, 0, accessFile.length());
        }
    }

    public static long countMultiRangeContentLength(List<Range> ranges, String boundary, String partContentType) {
        long totalLength = 0;
        for (Range r : ranges) {
            String boundaryLine = ("--" + boundary + "\r\n");
            String contentTypeLine = ("Content-Type: " + partContentType + "\r\n");
            String separate = "\r\n";
            String contentRange = "Content-Range: " + "bytes " + r.start + "-" + r.end + "/" + r.total + "\r\n";
            totalLength += boundaryLine.getBytes(StandardCharsets.UTF_8).length;
            totalLength += contentTypeLine.getBytes(StandardCharsets.UTF_8).length;
            totalLength += contentRange.getBytes(StandardCharsets.UTF_8).length;
            totalLength += separate.getBytes(StandardCharsets.UTF_8).length;
            totalLength += r.size();
            totalLength += separate.getBytes(StandardCharsets.UTF_8).length;
            System.out.print(boundaryLine);
            System.out.print(contentTypeLine);
            System.out.print(contentRange);
            System.out.print(separate);
            System.out.print("@content");
            System.out.print("\r\n");
        }
        totalLength += ("--" + boundary + "--").getBytes(StandardCharsets.UTF_8).length;
        System.out.print("--" + boundary + "--");
        return totalLength;
    }

    public static void main(String[] args) {
        List<Range> ranges = new ArrayList<>();
        ranges.add(new Range(0, 1023, 2048));
        ranges.add(new Range(1024, 2047, 2048));
        long total = countMultiRangeContentLength(ranges, "123456", "application/octet-stream");
        System.out.println("result:" + total);
    }

    private void writeRange(HttpServletResponse response, byte[] buffer, RandomAccessFile file, long start, long len) throws IOException {
        long toRead = len;
        file.seek(start);
        int i = 0;
        while ((toRead -= i) > 0 && ((i = file.read(buffer, 0, (int) Math.min(buffer.length, toRead))) != -1)) {
            response.getOutputStream().write(buffer, 0, i);
        }
    }

    public static class Range {
        long start;
        long end;
        long total;

        public Range() {
        }

        public Range(long start, long end, long total) {
            this.start = start;
            this.end = end;
            this.total = total;
        }

        private long size() {
            return end - start + 1;
        }
    }

    public static List<Range> parseRange(String value, long total) {
        try {
            value = value.trim();
            String[] kvs = value.split("=");
            if (!kvs[0].equals("bytes")) {
                return new ArrayList<>();
            }

            String[] parts = kvs[1].split(",");
            List<Range> list = new ArrayList<>();
            for (String part : parts) {
                String[] arr = part.split("-");
                Range range = new Range();
                range.start = Long.parseLong(arr[0].trim());
                range.end = arr.length > 1 ? Long.parseLong(arr[1].trim()) : total;
                range.total = total;
                list.add(range);
            }
            return list;
        } catch (Exception e) {
            return new ArrayList<>();
        }
    }
}
